package uk.ac.ebi.uniprot.wikipedia.retrievalServices;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import uk.ac.ebi.uniprot.wikipedia.exceptions.ServiceException;
import uk.ac.ebi.uniprot.wikipedia.util.RetrievalHelper;
import uk.ac.ebi.uniprot.wikipedia.model.GeneWikiConflictPage;
import uk.ac.ebi.uniprot.wikipedia.model.GeneWikiPage;
import uk.ac.ebi.uniprot.wikipedia.util.GeneWikiOrganism;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * Created with IntelliJ IDEA.
 * User: ljgarcia
 * Date: 11/06/12
 * Time: 15:22
 * To change this template use File | Settings | File Templates.
 */
public abstract class GeneWikiRetrieval {
    private static Logger logger = Logger.getLogger(GeneWikiRetrieval.class);
    protected Set<GeneWikiPage> geneWikiWithAccession = new TreeSet<GeneWikiPage>();
    protected Set<GeneWikiPage> geneWikiNoAccession = new TreeSet<GeneWikiPage>();
    protected Set<GeneWikiConflictPage> geneWikiConflict = new TreeSet<GeneWikiConflictPage>();

    protected String queryName;
    protected GeneWikiOrganism organism;

    /**
     * Destroy bean method; it will destroy the content of the collections.
     */
    protected void destroy() {
        this.geneWikiConflict.clear();
        this.geneWikiNoAccession.clear();
        this.geneWikiWithAccession.clear();
    }

    /**
     * Sets the organism the bean is going to work with, only one organism by bean.
     */
    public abstract void setOrganism();

    /**
     * Allow children classes to set the organism they work with; this method should be used in
     * the setOrganism method with a particular organism.
     * @param organism
     */
    protected void setOrganism(GeneWikiOrganism organism) {
        this.organism = organism;
    }

    /**
     * sets the bean property queryName.
     * @param queryName
     */
    protected void setQueryName(String queryName) {
        this.queryName = queryName;
    }

    /**
     * Returns the organism this class work with.
     * @return
     */
    public GeneWikiOrganism getOrganism() {
        return organism;
    }

    /**
     * Returns the name of the query this class work with.
     * @return
     */
    public String getQueryName() {
        return queryName;
    }
    /**
     * Returns a sorted set with genewiki pages (page, geneid, accession) for human organism.
     * @return
     * @throws java.io.IOException
     * @throws org.json.JSONException
     */
    public Set<GeneWikiPage> getGeneWikiWithAccession() throws IOException, JSONException, ServiceException {
//        this.init(this.organism, this.geneWikiWithAccession, geneWikiNoAccession, geneWikiConflict);
        return this.geneWikiWithAccession;
    }

    /**
     * Returns a sorted set with genewiki pages with no accession (page, geneid, null) for human organism.
     * @return
     * @throws IOException
     * @throws JSONException
     */
    public Set<GeneWikiPage> getGeneWikiNoAccession() throws IOException, JSONException, ServiceException {
//        if (!this.loaded) {
//            this.init(this.organism, this.geneWikiWithAccession, geneWikiNoAccession, geneWikiConflict);
//            this.loaded = true;
//        }
        return this.geneWikiNoAccession;
    }

    /**
     * Returns a sorted set with genewiki pages in conflict, ie more than one gene id per page, (page, geneid, accession) for human organism.
     * @return
     * @throws IOException
     * @throws JSONException
     */
    public Set<GeneWikiConflictPage> getGeneWikiConflict() throws IOException, JSONException, ServiceException {
//        if (!this.loaded) {
//            this.init(this.organism, this.geneWikiWithAccession, geneWikiNoAccession, geneWikiConflict);
//            this.loaded = true;
//        }
        return this.geneWikiConflict;
    }

    /**
     * Init all sets (with accession, no accession, in conflict) for a particular organism, human or mouse.
     *
     * @param organism
     * @throws IOException
     * @throws JSONException
     */
    protected void initCollections(GeneWikiOrganism organism) throws IOException, JSONException, ServiceException {
        geneWikiNoAccession.clear();
        geneWikiWithAccession.clear();
        geneWikiConflict.clear();
        String query;
        if (organism.equals(GeneWikiOrganism.HUMAN)) {
            query = RetrievalHelper.buildQuery(this.queryName, GeneWikiOrganism.HUMAN.getGeneIdProperty(), GeneWikiOrganism.HUMAN.getAccessionProperty());
        } else if (organism.equals(GeneWikiOrganism.MOUSE)) {
            query = RetrievalHelper.buildQuery(this.queryName, GeneWikiOrganism.MOUSE.getGeneIdProperty(), GeneWikiOrganism.MOUSE.getAccessionProperty());
        } else {
            throw new ServiceException("No information can be retrieved for unknown organisms: " + organism.getOrganism());
        }
        this.retrieveQueryResults(query, geneWikiWithAccession, geneWikiNoAccession, geneWikiConflict);
    }

    /**
     * Retrieves page, gene id, and accession from the GeneWiki sparql endpoint for a particular organism, human or mouse;
     * it also fills up the information in the three given sets (with accession, no accession, in conflict)
     * @param query
     * @param withAccession
     * @param noAccession
     * @param conflict
     * @throws IOException
     * @throws JSONException
     */
    private void retrieveQueryResults(String query, Set<GeneWikiPage> withAccession, Set<GeneWikiPage> noAccession, Set<GeneWikiConflictPage> conflict) throws IOException, JSONException {
        String urlQuery = RetrievalHelper.buildQuery("SPARQL", URLEncoder.encode(query, "UTF-8"));
        logger.debug("--GENE WIKI QUERY--" + query + " converted to " + urlQuery);
        URL url = new URL(urlQuery);
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        InputStream is;
        if (connection.getResponseCode() >= 400) {
            throw new IOException("ERROR RESPONSE CODE (" + connection.getResponseCode() + ") - ");
        } else {
            is = connection.getInputStream();
        }

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        //BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
        StringBuilder buffer = new StringBuilder ();
        String line;
        while ((line = reader.readLine()) != null) {
            buffer.append(line);
        }
        //logger.debug(buffer);
        JSONObject json = new JSONObject(buffer.toString());
        JSONArray array = json.getJSONObject("results").getJSONArray("bindings");
        for (int i = 0; i < array.length(); i++) {
            JSONObject elem = array.getJSONObject(i);
            GeneWikiPage gw = new GeneWikiPage();
            gw.setWikiURL(elem.getJSONObject("s").getString("value"));
            gw.setGeneId(elem.getJSONObject("entrezId").getString("value"));
            try {
                gw.setAccession(elem.getJSONObject("upAcc").getString("value"));
                boolean added = withAccession.add(gw);
                if (!added) {
                    Iterator<GeneWikiPage> itr = withAccession.iterator();
                    while (itr.hasNext()) {
                        GeneWikiPage gwp = itr.next();
                        if (gwp.getGeneId().equals(gw.getGeneId())) {
                            //if (gwp.getWikiURL().equals(gw.getWikiURL())) {//page is the same one so, one page multiple genes
                                conflict.add(new GeneWikiConflictPage(gwp));
                                conflict.add(new GeneWikiConflictPage(gw));
                                withAccession.remove(gwp);
                            //}
                            break;
                        }
                    }
                }
            } catch (JSONException e) {
                boolean added = noAccession.add(gw);
                if (!added) {
                    conflict.add(new GeneWikiConflictPage(gw));
                    Iterator<GeneWikiPage> itr = noAccession.iterator();
                    while (itr.hasNext()) {
                        GeneWikiPage gwp = itr.next();
                        if (gwp.getGeneId().equals(gw.getGeneId())) {
                            conflict.add(new GeneWikiConflictPage(gwp));
                            withAccession.remove(gwp);
                            break;
                        }
                    }
                }
            }
        }
    }
}
/*
{
  "head": { "link": [], "vars": ["s", "entrezId", "upAcc"] },
  "results": {
    "distinct": false,
    "ordered": true,
    "bindings": [
      {
        "s": { "type": "uri", "value": "http://genewikiplus.org/wiki/Special:URIResolver/DDX20" }	,
        "entrezId": { "type": "literal", "value": "11218" }	,
        "upAcc": { "type": "literal", "value": "Q9UHI6" }
      },
      {
        "s": { "type": "uri", "value": "http://genewikiplus.org/wiki/Special:URIResolver/SDCBP2" }	,
        "entrezId": { "type": "literal", "value": "27111" }	,
        "upAcc": { "type": "literal", "value": "Q9H190" }
      }
    ]
  }
}
*/